package xfuzzy.xfhl.model.hierStruct;

import java.util.LinkedList;
import java.util.Stack;
import java.util.Queue;

/**
 * Estructura Jerarquica
 * 
 * @author Sergio Cala Cordero, Septiembre 2009, Herramienta XFHL
 * 
 */
public class XfhlHierStruct {

	/**
	 * Modulo de la Estructura Jerarquica
	 */
	private XfhlINode modulo;

	/**
	 * Numero de Variables de la estructura jerarquica El numero de variables
	 * aumenta en 2 cada vez que añadimos un nivel
	 */
	private int numVar;

	/**
	 * Numero de modulos de la estructura jerarquica
	 */
	private int numModulos;

	/**
	 * Variable estatica que nos dira de cuantas entradas queremos las
	 * estructuras
	 */
	private static int numVarEnt;

	/**
	 * Pila estatica que la utilizaremos para almacenar las estructuras que nos
	 * quedan por visitar
	 */
	private static Stack<XfhlHierStruct> pila;

	/**
	 * Cola que nos dira que nodos quedan por visitar. La utilizaremos cuando
	 * queremos iterar para conseguir estructuras de numVarEnt entradas
	 */
	private Queue<XfhlINode> cola = new LinkedList<XfhlINode>();

	/**
	 * Constructor por defecto
	 */
	public XfhlHierStruct() {
		this.modulo = null;
		this.numVar = -1;
		this.numModulos = -1;
	}

	/**
	 * Constructor
	 * 
	 * @param m
	 *            Modulo de la estructura
	 * @param conVar
	 *            Numero de variables de la estructura
	 * @param numModulos
	 *            Numero de modulos de la estructura
	 */
	public XfhlHierStruct(XfhlINode m, int conVar, int numModulos) {
		this.modulo = m;
		this.numModulos = numModulos;
		this.numVar = conVar;
		// encolamos la raiz de nuestro modulo, para poder iterar
		this.cola.add(this.modulo);
	}

	/**
	 * Metodo que itera y nos devuelve la siguiente estructura posible. Son
	 * todas las estructuras con una variable de entrada mas.
	 * 
	 * @return Nos devuelve la siguiente estructura jerarquica. NULL cuando ya
	 *         las hemos recorrido todas
	 */
	private XfhlHierStruct Iterador() {

		XfhlHierStruct sal = null;
		if (!this.cola.isEmpty()) {
			// desencolamos el modulo
			XfhlINode iter = this.cola.remove();

			/* Permutacion del nodo raiz */
			if (iter instanceof XfhlModule && ((XfhlModule) iter).getIdMod() == ((XfhlModule) this.modulo).getIdMod()) {
				// Copiamos el Modulo actual
				XfhlINode copia = this.modulo.copiar();
				// Salvamos el XfhlNode de salida
				XfhlNode nodeOutput = ((XfhlNode) ((XfhlModule) copia).getSal());

				// Nos creamos el nuevo nodo que va a sustituir al de salida
				// El identificador de la variable interna es numModulos-1
				XfhlNode nodeInner = new XfhlNode(numModulos - 1, XfhlNode.INNER);

				// Asignamos el nodo interno como salida a la copia
				((XfhlModule) copia).setSal(nodeInner);

				// Nos creamos el nuevo nodo de entrada
				XfhlNode nodeInput = new XfhlNode(numModulos + 1, XfhlNode.INPUT);

				// Creamos el nuevo Modulo con la permutaci�n raiz
				XfhlINode modN = new XfhlModule(nodeInput, copia, nodeOutput, numModulos + 1);

				// Nos creamos la estructura a devolver
				XfhlHierStruct eRaiz = new XfhlHierStruct(modN, numVar + 2, numModulos + 1);
				// actualizamos la salida
				sal = eRaiz;
				// encolamos las dos entradas del raiz
				cola.add(((XfhlModule) (iter)).getEnt1()); // lo encolamos
				cola.add(((XfhlModule) (iter)).getEnt2()); // lo encolamos
			} else if (iter instanceof XfhlModule) {

				// Permutacion de otro modulo
				// encolamos las dos entradas del modulo
				cola.add(((XfhlModule) (iter)).getEnt1()); // lo encolamos
				cola.add(((XfhlModule) (iter)).getEnt2()); // lo encolamos

				/*
				 * Preguntamos si la entrada 1 es un Modulo; de ser asi tenemos
				 * que hacer una copia y llegar hasta el mismo sitio en la copia
				 * y crearnos una nueva estructura
				 */
				XfhlINode anadirM = ((XfhlModule) (this.modulo)).copiar(0, ((XfhlModule) (iter)).getIdMod(), this.numModulos + 1);
				XfhlHierStruct eNueva = new XfhlHierStruct(anadirM, this.numVar + 2, this.numModulos + 1);
				sal = eNueva;
			} else { /* Convertimos un nodo en modulo */
				// Con el Nodo hacemos un nuevo modulo donde el nodo inicial es
				// la salida
				XfhlINode anadirM = ((XfhlModule) (this.modulo)).copiar(1, ((XfhlNode) (iter)).getValor(),
						this.numModulos + 1);
				XfhlHierStruct eNueva = new XfhlHierStruct(anadirM, this.numVar + 2, this.numModulos + 1);
				sal = eNueva;
			}
		}
		return sal;
	}

	/**
	 * Metodo estatico que utilizaremos para inicializar nuestros metodos
	 * estaticos que nos proporcionaran estructuras jerarquicas de un
	 * determinado numero de entrada
	 * 
	 * @param numVE
	 */
	public static void setNumVarEnt(int numVE) {
		// Nos creamos la pila
		pila = new Stack<XfhlHierStruct>();

		// Inicializamos el numero de variables de la estructura
		numVarEnt = numVE;

		// Partimos de una estructura basica
		XfhlINode ent1 = new XfhlNode(0, XfhlNode.INPUT);
		XfhlINode ent2 = new XfhlNode(1, XfhlNode.INPUT);
		XfhlINode sal = new XfhlNode(0, XfhlNode.OUTPUT);

		// Identificador del modulo es 1
		XfhlINode ini = new XfhlModule(ent1, ent2, sal, 1);

		// Nos creamos la primera estructura que tiene 3 variables y 1 modulo
		XfhlHierStruct estrJerBas = new XfhlHierStruct(ini, 3, 1);

		// apilamos la estructura inicial
		pila.push(estrJerBas);
	}

	/**
	 * Metodo estatico que nos proporciona la siguiente XfhlHierStruct con el
	 * numero de entradas deseada
	 * 
	 * @return La siguiente estructura jerarquica
	 */
	public static XfhlHierStruct dameEstructura() {
		// Variable de salida
		XfhlHierStruct sal = null;

		if (!pila.empty()) {
			// consultamos el tope
			XfhlHierStruct aux = pila.peek();
			// el siguiente del tope de la pila
			XfhlHierStruct sig = null;

			// Buscamos el siguiente que no sea vacio
			while (!pila.empty() && (sig = aux.Iterador()) == null) {
				// desapilo
				pila.pop();
				// consultamos el tope si no esta vacia
				if (!pila.empty())
					aux = pila.peek();
			}

			// Comprobamos que no hayamos terminado
			if (!pila.empty()) {

				// Comprobamos si tenemos que devolver siguiente
				while (((sig.numVar + 1) / 2) != numVarEnt) {
					// si el siguiente en el que estoy no lo tengo que devolver
					// tengo que apilarlo
					pila.push(sig);
					sig = sig.Iterador();
				}
				sal = sig;
			}
		}

		return sal;

	}

	/**
	 * 
	 * GETTER AND SETTER
	 */
	public XfhlINode getModulo() {
		return this.modulo;
	}

	public void setModulo(XfhlINode m) {
		this.modulo = m;
	}

	public int getNumModulos() {
		return this.numModulos;
	}

	public int getNumVar() {
		return this.numVar;
	}

	/**
	 * Metodo estatico que calcula el numero de estructuras jerarquica
	 * diferentes con un numero de variables
	 * 
	 * @param nVar
	 *            Numero de variables de entrada
	 * @return El numero de estructuras
	 */
	public static int numEstructuras(int nVar) {
		if (nVar == 2)
			return 1;
		else
			return numEstructuras(nVar - 1) * (2 * nVar - 3);
	}
}
